/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.propagation.sampling;

import ore.SpacecraftState;
import ore.errors.OrekitException;
import ore.errors.PropagationException;
import ore.time.AbsoluteDate;

/**
 * This class wraps an object implementing {@link OrekitFixedStepHandler}
 * into a {@link OrekitStepHandler}.
 * 
 * This class is like the <code>StepNormalizer</code> interface in
 * Commons Math, but integrates with the space dynamics component
 * hierarchy.
 * 
 * @author Luc Maisonobe
 */
public class OrekitStepNormalizer 
    extends Object
    implements OrekitStepHandler
{
    /** 
     * Fixed time step.
     */
    private double h;
    /** 
     * Underlying step handler.
     */
    private OrekitFixedStepHandler handler;
    /** 
     * Last step date.
     */
    private AbsoluteDate lastDate;
    /** 
     * Last State vector.
     */
    private SpacecraftState lastState;
    /** 
     * Integration direction indicator.
     */
    private boolean forward;


    /** 
     * @param h Fixed time step (sign is not used)
     * @param handler Fixed time step handler to wrap
     */
    public OrekitStepNormalizer(double h, OrekitFixedStepHandler handler) {
        super();
        this.h       = Math.abs(h);
        this.handler = handler;
        this.reset();
    }


    /** 
     * This handler needs dense output in order to provide data at
     * regularly spaced steps regardless of the steps the propagator
     * uses, so this method always returns true.
     * 
     * @return Always true
     */
    public boolean requiresDenseOutput() {
        return true;
    }
    /** 
     * Initialize the internal data as required before the first step is
     * handled.
     */
    public void reset() {
        this.lastDate  = null;
        this.lastState = null;
        this.forward   = true;
    }
    /**
     * Handle the last accepted step.
     * 
     * @param interpolator Interpolator for the last accepted step. For
     * efficiency purposes, the various propagators reuse the same
     * object on each call, so if the instance wants to keep it across
     * all calls (for example to provide at the end of the propagation a
     * continuous model valid throughout the propagation range), it
     * should build a local copy using the clone method and store this
     * copy.
     * @param isLast True if the step is the last one
     * 
     * @throws PropagationException This exception is propagated to the
     * caller if the underlying user function triggers one
     */
    public void handleStep(OrekitStepInterpolator interpolator, boolean isLast)
        throws PropagationException
    {
        try {
            if (this.lastState == null) {
                /*
                 * Initialize lastState in the first step case
                 */
                this.lastDate = interpolator.getPreviousDate();
                interpolator.setInterpolatedDate(this.lastDate);
                this.lastState = interpolator.getInterpolatedState();
                /*
                 * Take the propagation direction into account
                 */
                this.forward = (interpolator.getCurrentDate().compareTo(this.lastDate) >= 0);
                if (!this.forward) {
                    this.h = -(this.h);
                }
            }
            /*
             * Use the interpolator to push fixed steps events to the underlying handler
             */
            AbsoluteDate nextTime = new AbsoluteDate(this.lastDate, this.h);
            boolean nextInStep = (this.forward ^ (nextTime.compareTo(interpolator.getCurrentDate()) > 0));
            while (nextInStep) {
                /*
                 * Output the stored previous step
                 */
                this.handler.handleStep(lastState, false);
                /*
                 * Store the next step
                 */
                this.lastDate = nextTime;
                interpolator.setInterpolatedDate(this.lastDate);
                this.lastState = interpolator.getInterpolatedState();
                /*
                 * Prepare next iteration
                 */
                nextTime = new AbsoluteDate(nextTime, this.h);
                nextInStep = (this.forward ^ (nextTime.compareTo(interpolator.getCurrentDate()) > 0));
            }

            if (isLast) {
                /*
                 * There will be no more steps,
                 * the stored one should be flagged as being the last
                 */
                this.handler.handleStep(lastState, true);
            }
        }
        catch (OrekitException oe) {
            /*
             * Recover a possible embedded PropagationException
             */
            for (Throwable t = oe; t != null; t = t.getCause()) {
                if (t instanceof PropagationException) {
                    throw (PropagationException) t;
                }
            }
            throw new PropagationException(oe.getMessage(), oe);
        }
    }
}
